﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;
using System.Management;

namespace SSFT
{
	public class SingleFileServer
	{
		int				m_port;
		string			m_filepath;

		volatile bool	runflag;
		Thread			listen_thread;
		List<Connection> connections;
		int				max_connections;
		List<TcpListener> ip4_ip6_listeners;

		const int DefaultMaxConnections = 10;

		public SingleFileServer()
        {
			connections = new List<Connection>();
			max_connections = DefaultMaxConnections;
		}

		public bool IsRunning
		{
			get { return listen_thread != null || HasConnections; }
		}

		public int MaxConnections
		{
			get { return max_connections; }
			set
			{
				if (value > -1)
					max_connections = value;
			}
		}

		public int Port
		{
			set { m_port = value; }
			get { return m_port; }
		}

		public string Filepath
		{
			set { m_filepath = value; }
			get { return m_filepath; }
		}

		public bool HasConnections
		{
			get { return connections.Count > 0; }
		}

		public List<Connection> Connections
		{
			get { return connections; }
		}

		public void Start(int port, string filepath)
		{
			if (IsRunning)
				return;

			m_port = port;
			m_filepath = filepath;

			// Start the listener thread.
			listen_thread = new Thread(new ThreadStart(Run));
			listen_thread.Start();
			Log("Starting server...");
		}

		public void Stop()
		{
			if (!IsRunning)
				return;

			// Make the listener thread stop.
			runflag = false;
			Log("Server shutting down...");
		}

		private void Run()
		{
			ip4_ip6_listeners = new List<TcpListener>(2);

			try
			{
				// Create ip4 and ip6 tcp listeners.
				ip4_ip6_listeners.Add(new TcpListener(IPAddress.Any, m_port));
				ip4_ip6_listeners.Add(new TcpListener(IPAddress.IPv6Any, m_port));
				foreach (var listener in ip4_ip6_listeners)
					listener.Start();

				runflag = true;
				Program.Form.ServerOptionsEnabled = false;
				Log("Server up");

				// Start main server loop.
				while (runflag)
				{
					// If a new connection is pending, start a new connection thread.
					foreach (var listener in ip4_ip6_listeners)
						if (listener.Pending())
						{
							// Only accept connection if we have room.
							if (max_connections == 0 || connections.Count < max_connections)
							{
								Connection connection = new Connection(this);
								connection.Start(listener.AcceptTcpClient());
								connections.Add(connection);
							}
						}
					Thread.Sleep(100);
				}
			}
			catch (Exception ex)
			{
				Log(Util.ExceptionToString(ex));
			}
			finally
			{
				// Stop ip4 and ip6 tcp listeners.
				foreach (var listener in ip4_ip6_listeners)
					listener.Stop();

				// Stop transfers.
				foreach (var connection in connections)
					if (connection.IsRunning)
						connection.Stop();

				// Wait for transfers to stop.
				while (HasConnections)
					Thread.Sleep(100);

				Program.Form.ServerOptionsEnabled = true;
				Log("Server down");

				// Reset all states.
				runflag = false;
				listen_thread = null;
			}
		}

		private void Log(string msg)
		{
			Program.Form.ServerLog(msg);
		}

		public class Connection
		{
			SingleFileServer m_server;
			TcpClient		m_client;

			bool			runflag;
			Thread			work_thread;
			ProgressInfo	progress;
			BinaryFormatter	serializer;

			const int		BufferSize = 1024;
			const int		TimeoutPeriod = 60000;

			public Connection(SingleFileServer server)
			{
				m_server = server;
				serializer = new BinaryFormatter();
			}

			public bool IsRunning
			{
				get { return work_thread != null; }
			}

			public IPEndPoint Endpoint
			{
				get
				{
					if (m_client != null)
						return m_client.Client.RemoteEndPoint as IPEndPoint;
					return null;
				}
			}

			public ProgressInfo Progress
			{
				get { return progress; }
			}

			public void Start(TcpClient client)
			{
				if (IsRunning)
					return;

				// Set up tcp client.
				m_client = client;
				m_client.SendTimeout = TimeoutPeriod;
				m_client.ReceiveTimeout = TimeoutPeriod;

				// Start the transfer thread.
				work_thread = new Thread(new ThreadStart(Run));
				work_thread.Start();
			}

			public void Stop()
			{
				if (!IsRunning)
					return;

				// Make the transfer thread stop.
				runflag = false;
			}

			public void Run()
			{
				byte[]		buffer = new byte[BufferSize];
				int			num_bytes;
				Stream		fstream = null, netstream = null;
				FileHeader	hdr = new FileHeader();

				try
				{
					runflag = true;

					// Add to gui.
					Program.Form.ServerAddConnection(this);

					// Build file header.
					hdr.m_filename = Path.GetFileName(m_server.Filepath);
					hdr.m_filesize = new FileInfo(m_server.Filepath).Length;

					// Determine ip endpoint.
					Log(Endpoint.Address.ToString() +
						" connected");

					// Create streams.
					fstream = new FileStream(m_server.Filepath, FileMode.Open, FileAccess.Read);
					netstream = m_client.GetStream();

					// Send header and wait for ready host.
					serializer.Serialize(netstream, hdr);

					// Wait for the client to be ready to receive.
					netstream.Read(buffer, 0, 1);

					// Create a progress object for the gui.
					progress = new ProgressInfo();
					progress.Filename = m_server.Filepath;
					progress.Filesize = new FileInfo(m_server.Filepath).Length;

					// Now host is ready, start sending file.
					Log("File transfer starting...");
					while (fstream.Position < fstream.Length)
					{
						if (!runflag)
							throw new Exception("File transfer interrupted");

						num_bytes = fstream.Read(buffer, 0, BufferSize);
						netstream.Write(buffer, 0, num_bytes);

						progress.BytesTransferred = fstream.Position;
					}
					Log("File transfer complete");
				}
				catch (Exception ex)
				{
					Log(Util.ExceptionToString(ex));
				}
				finally
				{
					Log(Endpoint.Address.ToString() +
						" disconnected");

					if (fstream != null)
						fstream.Close();
					if (m_client != null)
						m_client.Close();

					// Reset all states.
					progress = null;
					runflag = false;
					work_thread = null;

					// Remove ourself from server list.
					if (m_server.Connections.Contains(this))
						m_server.Connections.Remove(this);

					// Remove from gui.
					Program.Form.ServerRemoveConnection(this);
				}
			}

			private void Log(string msg)
			{
				Program.Form.ServerLog(msg);
			}
		}
	}
}
